﻿/*
 * Copyright the original author or authors.
 * 
 * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.mozilla.org/MPL/MPL-1.1.html
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ 
import com.bourre.log.PixlibStringifier;
import com.bourre.transitions.IFrameListener;
import com.bourre.events.IEvent;

import com.kairos.engine.RTBeacon;
import com.kairos.engine.RTEvent;
import com.kairos.engine.RTAnimation;
import com.kairos.log.KairosDebug;
import com.bourre.commands.AbstractSyncCommand;

/**
 * Abstract object to create real-time based objects.
 * 
 * <p>{@code RTObject} provide basic methods to start and stop
 * animations within Kairos.</p>
 *	
 * @author	Cédric Néhémie
 * @version	1.0
 * @see		IFrameListener	
 * @example import com.bourre.transitions.IFrameListener;
 * 
 * import com.kairos.engine.RTEvent;
 * import com.kairos.engine.RTBeacon;
 * import com.kairos.engine.RTObject;
 * 
 * class Mover extends RTObject implements IFrameListener
 * {
 * 	private var _oTarget : MovieClip; // Object to move.
 * 	private var _nSpeed : Number; // Speed in px/s.
 * 
 * 	public function Mover ( o : MovieClip, speed : Number )
 * 	{
 * 		_nSpeed = speed > 0 ? speed : 1;
 *		_oTarget = o;
 * 		
 * 		// registering the object as listener of the RTBeacon		
 * 		start();
 * 	} 
 * 	public function onEnterFrame () : Void
 * 	{
 * 		// current speed computed with the RTEvent.getStepSecond() method
 * 		var curSpeed : Number = _nSpeed * ( RTEvent ( arguments[ 0 ] ).getStepSecond() ); 
 * 
 * 		o.x += curSpeed;
 * 	}
 * }
 */
class com.kairos.engine.RTObject extends AbstractSyncCommand implements RTAnimation
{
	private var _bIP : Boolean;
	private var _oBeacon : RTBeacon;
	private var _nLocalSpeed : Number;
	
	/**
	 * Private constructor. 
	 */
	private function RTObject ()	
	{
		_bIP = false;
		_oBeacon = RTBeacon.getInstance();
		_nLocalSpeed = 1;
	}
	
	/**
	 * Implementation of the {@code IFrameListener} interface. 
	 * 
	 * <p>Override it to create your own object, the {@code onEnterFrame}
	 * method is called with a {@link RTEvent} object as argument.</p>
	 */
	public function onEnterFrame () : Void 
	{}
	
	/**
	 * {@link #start} alias.
	 * 	
	 * @param	e	{@code Command} implementation
	 * @see	#start
	 */
	public function execute( e:IEvent ) : Void 
	{
		start();
	}
	
	/**
	 * Start the animation.
	 * 
	 * <p>If the object is allready playing the function don't
	 * do anything.</p>
  	 */
	public function start () : Void
	{
		if( !_bIP )
		{
			_bIP = true;
			_oBeacon.addFrameListener ( this );
		}
	}
	
	/**
	 * {@link #start} alias.
	 * 
	 * @see #start
	 */
	public function play() : Void
	{
		start ();
	}
	
	/**
	 * Stop the animation.
	 * 
	 * <p>If the object is allready stopped the function don't
	 * do anything.</p>
	 */
	public function stop () : Void
	{
		if( _bIP )
		{
			_bIP = false;
			_oBeacon.removeFrameListener ( this );
		}
	}
	
	/**
	 * Is the {@code RTObject} currently playing ?
	 */
	public function isPlaying () : Boolean
	{
		return _bIP;
	}
	
	/**
	 * Defines a new {@code FrameBeacon} object the object will run on.
	 * 
	 * <p>Use that method carefully, if you have objects in your scene witch 
	 * are plugged on two beacon witch have differents settings you can loose
	 * synchronisation between those objects.</p>
	 * 
	 * <p>If the passed-in beacon is null, the function failed and throw an error
	 * on the Kairos log channel.</p>
	 * 
	 * @param  beacon {@code FrameBeacon} to register with.
	 * @throws Error The passed-in beacon is null.
	 */
	public function setFrameBeacon( beacon : RTBeacon ) : Void 
	{
		if( !beacon )
		{
			KairosDebug.ERROR( "Attempting to define a null beacon in " + this );
			throw new Error ( "Attempting to define a null beacon in " + this );
		}
		
		if( !_bIP )
		{
			_oBeacon = beacon;
		}
		else
		{
			stop();
			_oBeacon = beacon;
			start();
		}
	}

	/**
	 * Returns the {@code RTBeacon} used by the current object.
	 * 
	 * @return {@code RTBeacon} used by the current object.
	 */
	public function getFrameBeacon() : RTBeacon 
	{
		return _oBeacon;
	}
	
	/**
	 * Defines the local speed factor for the current object. Local speed works
	 * as a multiplier for the time step value received from the beacon - as the
	 * {@code setGlobalSpeed} feature of the RTBeacon.
	 * 
	 * <p>The default {@code setLocalSpeed} method don't support value lower
	 * or equals than 0, override it if you want to avoid negative values.</p>
	 * 
	 * @param 	rate float number used as multiplier for the current time step
	 * @throws 	Error The value specified in {@code setLocalSpeed}
	 * 			isn't supported by the current object.	
	 */
	public function setLocalSpeed(rate : Number) : Void 
	{
		if( rate <= 0 )
		{
			KairosDebug.ERROR( this + " don't support local speed lower or equals than 0 : " + rate );
			throw new Error ( this + " don't support local speed lower or equals than 0 : " + rate );
		}
		
		_nLocalSpeed = rate;
	}
	/**
	 * Returns the current local speed factor of the object.
	 * 
	 * @return {@code Number} local speed of the object.
	 */
	public function getLocalSpeed() : Number
	{
		return _nLocalSpeed;
	}
	
	/**
	 * Returns the time step passed in the {@code onEnterFrame} events
	 * in local timescale.
	 * 
	 * <p>Use that method in the {@code onEnterFrame} method to automatically
	 * convert the time step from global to local time scale. The example below show
	 * how simple it is to get the real step value to use in your equations.</p>
	 * 
	 * <code>public function onEnterFrame ( e : IEvent ) : void
	 * {
	 * 	var step : Number = getLocalStep( e );
	 * 
	 * 	// do whatever you want.
	 * }</code>
	 * 
	 * <p>Basically the returned value is the {@code RTEvent.getStep()} return
	 * multiplied by the {@code _nLocalSpeed} value of the object.
	 * If the passed-in event isn't a {@code RTEvent} the function failed
	 * and throw a {@code TypeError}.</p>
	 * 
	 * @param 	e	{@code Event} passed by the {@code RTBeacon} in the
	 * 				{@code onEnterFrame} event. It's not necessary to cas
	 * 				the event as a {@code RTEvent}, the method will
	 * 				do it automatically.
	 * @return 	The timestep in the local time scale.
	 * @throws 	Error The passed-in {@code IEvent} isn't a valid
	 * 		 	{@code RTEvent} instance. 
	 */
	private function getLocalStep ( e : IEvent ) : Number
	{
		_checkEventType( e );
		return RTEvent( e ).getStep() * _nLocalSpeed;
	}
	
	/**
	 * Returns the time step in seconds passed in the {@code onEnterFrame}
	 * events in local timescale.
	 * 
	 * <p>Use that method in the {@code onEnterFrame} method to automatically
	 * convert the time step from global to local time scale. The example below show
	 * how simple it is to get the real step value to use in your equations.</p>
	 * 
	 * <code>public function onEnterFrame ( e : IEvent ) : void
	 * {
	 * 	var stepInSecond : Number = getLocalStepInSecond( e );
	 * 
	 * 	// do whatever you want.
	 * }</code>
	 * 
	 * <p>Basically the returned value is the {@code RTEvent.getStepInSecond()}
	 * return multiplied by the {@code _nLocalSpeed} value of the object.
	 * If the passed-in event isn't a {@code RTEvent} the function failed
	 * and throw an {@code Error}.</p>
	 * 
	 * @param 	e	{@code IEvent} passed by the {@code RTBeacon} in the
	 * 				{@code onEnterFrame} event. It's not necessary to cas
	 * 				the event as a {@code RTEvent}, the method will
	 * 				do it automatically.
	 * @return 	The timestep in the local time scale.
	 * @throws  Error The passed-in {@code IEvent} isn't a valid
	 * 			{@code RTEvent} instance.
	 */
	private function getLocalStepInSecond ( e : IEvent ) : Number
	{
		_checkEventType( e );
		return RTEvent( e ).getStepInSecond() * _nLocalSpeed;
	}
	
	/**
	 * Check if the passed-in event is a valid RTEvent instance.
	 */
	private function _checkEventType ( e : IEvent ) : Void
	{
		if( !( e instanceof RTEvent ) )
		{
			KairosDebug.ERROR( e + " isn't a RTEvent object, " + this + ".getLocalStepInSecond() failed." );
			throw new Error ( e + " isn't a RTEvent object, " + this + ".getLocalStepInSecond() failed." );
		}
	}
	
	/**
	 * Return the String representation of the object. 
	 */	
	public function toString() : String
	{
		return PixlibStringifier.stringify( this );
	}

}